// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2025-01-08

package container

import (
	"gitee.com/go-libs/perm/types"
	"sort"
)

type (
	// Container is a component interface that store applications, application
	// resources, application roles and application permissions. It is used to
	// verify whether one of given roles is authorized to the given resource.
	Container interface {
		// GetApplicationContainer read the application container from the
		// container, return nil and false if it does not exist.
		GetApplicationContainer(tye types.Application) (ac ApplicationContainer, has bool)

		// SetApplicationContainer add an application container to the container,
		// Return error if it already exists.
		SetApplicationContainer(ac ApplicationContainer) (err error)

		// GetApplicationContainers read all application containers from the
		// container that sorted by application type name.
		GetApplicationContainers() (ac []ApplicationContainer)

		// Verify whether the roles in the list are authorized to resource of
		// the application. If any role is authorized to verify successfully.
		Verify(application types.Application, resource types.Resource, roles ...types.Role) types.Verification
	}

	container struct {
		Applications     map[types.Application]ApplicationContainer
		ApplicationsKeys []string
	}
)

func NewContainer() Container {
	return &container{
		Applications:     make(map[types.Application]ApplicationContainer),
		ApplicationsKeys: make([]string, 0),
	}
}

func (o *container) GetApplicationContainer(tye types.Application) (ac ApplicationContainer, has bool) {
	ac, has = o.Applications[tye]
	return
}

func (o *container) GetApplicationContainers() (ac []ApplicationContainer) {
	ac = make([]ApplicationContainer, 0)
	for _, k := range o.ApplicationsKeys {
		if v, ok := o.Applications[types.Application(k)]; ok {
			ac = append(ac, v)
		}
	}
	return
}

func (o *container) SetApplicationContainer(ac ApplicationContainer) (err error) {
	// Return error if it already exists.
	if _, ok := o.Applications[ac.GetApplication()]; ok {
		err = types.ErrApplicationRepeated
		return
	}

	// Update applications mapping.
	o.Applications[ac.GetApplication()] = ac
	o.ApplicationsKeys = append(o.ApplicationsKeys, ac.GetApplication().String())

	// Sort by names.
	if len(o.ApplicationsKeys) > 0 {
		sort.Strings(o.ApplicationsKeys)
	}
	return
}

func (o *container) Verify(application types.Application, resource types.Resource, roles ...types.Role) types.Verification {
	// Run application verifier.
	if ac, ok := o.Applications[application]; ok {
		return ac.Verify(resource, roles...)
	}

	// The application does not exist.
	return types.VerificationApplicationNotFound
}
